Why Dart First
Dart is the language behind Flutter. It is statically typed with type inference, supports sound null safety, and mixes imperative, object-oriented, and functional styles. Mastering basic types and operators makes reading Flutter examples and building app logic straightforward.
Understanding Dart fundamentals is essential because every Flutter widget, every state management pattern, and every piece of business logic you write will use Dart syntax and concepts. The language's design emphasizes clarity, safety, and developer productivity.
Basic Data Types and Declarations
Dart provides several built-in data types and multiple ways to declare variables. Understanding these is fundamental to writing effective Dart code.
int
Whole numbers (e.g., 0, 42, -7)
double
Floating-point numbers (e.g., 3.14)
String
Text (e.g., "Afrilen")
bool
Boolean values: true/false
List<T>
Ordered collection (e.g., List<int>)
Map<K, V>
Key/value pairs (e.g., Map<String, dynamic>)
dynamic
Runtime-typed value (use sparingly)
Variable Declaration Keywords
var
Compiler infers the type at assignment time. Use when the type is obvious from the value.
final
Single-assignment variable (value set once). Use when a value won't change after construction but may be computed at runtime.
const
Compile-time constant (immutable and canonicalized). Use for values known at compile time (literals, const constructors).
Code Pattern: Declaration Examples
int count = 10;
double price = 9.99;
String title = 'Mobile Development';
bool isOpen = true;
var name = 'Adu'; // inferred as String
final createdAt = DateTime.now(); // runtime constant
const maxLimit = 100; // compile-time constant
Best Practices:
- Use
finalwhen a value won't change after construction but may be computed at runtime. - Use
constfor values known at compile time (literals, const constructors). - Prefer explicit types in public APIs for clarity.
Null Safety Basics
Dart's null safety feature helps prevent null reference errors, which are a common source of bugs in many programming languages. Understanding null safety is crucial for writing robust Dart code.
Non-nullable by Default
By default, types are non-nullable (e.g., String name cannot be null). This prevents many common null reference errors.
Nullable Types
Use nullable types with ? (e.g., String? nickname) when a value might be null.
Null Assertion Operator
Use the null assertion operator ! to assert non-null (use sparingly and only when you're certain the value is not null).
Null Coalescing
Use ?? to provide default values when a nullable variable might be null.
Examples
String? nickname; // can be null
var displayName = nickname ?? 'Guest'; // default to 'Guest'
print(displayName);
Strings and Interpolation
Dart provides powerful string manipulation capabilities. You can concatenate strings or use string interpolation for more readable code.
Concatenation
Use the + operator to concatenate strings:
String greeting = 'Hello' + ' ' + 'World';
String Interpolation
Use ${expression} or $variable for interpolation (preferred method):
String first = 'Adu';
String last = 'Amankwah';
String full = '$first $last'; // interpolation
print('Welcome, $full');
Note: String interpolation is generally preferred over concatenation as it's more readable and efficient.
Collections: List and Map
Collections are essential data structures in Dart. Lists store ordered sequences of items, while Maps store key-value pairs.
List
Lists are ordered collections of items. You can create them using list literals or typed constructors:
// List literal
var nums = [1, 2, 3];
// Typed variant
List<String> names = [];
// Accessing elements
print(nums[0]); // first element (index 0)
Map
Maps store key-value pairs. They're useful for representing structured data:
// Map literal
var person = {'name': 'Adu', 'age': 22};
// Typed variant
Map<String, dynamic> item = {};
// Accessing values
print(person['name']); // value by key
Operators
Dart provides a comprehensive set of operators for performing various operations on values.
Arithmetic Operators
Basic mathematical operations:
+- Addition-- Subtraction*- Multiplication/- Division (returns double)~/- Integer division%- Modulus (remainder)
int a = 7;
int b = 3;
print(a + b); // 10
print(a / b); // 2.3333333333
print(a ~/ b); // 2 (integer division)
print(a % b); // 1 (remainder)
Comparison Operators
These operators return bool values:
==- Equal to!=- Not equal to>- Greater than<- Less than>=- Greater than or equal to<=- Less than or equal to
Logical Operators
&&- Logical AND (both conditions must be true)||- Logical OR (at least one condition must be true)!- Logical NOT (negates a boolean value)
Assignment and Compound Operators
=- Assignment+=- Add and assign-=- Subtract and assign*=- Multiply and assign/=- Divide and assign??=- Assign if null
Example with Logic
bool hasAccess = (age >= 18) && isOpen;
Basic I/O in Dart (Console Programs)
Dart console I/O uses dart:io. For beginner exercises and quick utilities, use stdin.readLineSync() to read user input.
Example: Read and Print
import 'dart:io';
void main() {
stdout.write('Enter your name: ');
String? name = stdin.readLineSync();
print('Hello, ${name ?? 'Guest'}!');
}
Important Notes:
stdin.readLineSync()returnsString?; always handle null safely.stdout.write()doesn't add a newline;print()does.- Use the null coalescing operator
??to provide default values.
Example Utility: Rectangle Area (Step-by-Step)
Goal: Prompt user for width and height, compute area, display result.
Example Code
import 'dart:io';
void main() {
stdout.write('Enter width: ');
String? w = stdin.readLineSync();
stdout.write('Enter height: ');
String? h = stdin.readLineSync();
// Parse inputs safely
double width = double.tryParse(w ?? '') ?? 0.0;
double height = double.tryParse(h ?? '') ?? 0.0;
double area = width * height;
print('Area: $area');
}
Key Points:
- Use
double.tryParse()to avoid exceptions and provide a fallback value. - Always validate input and inform the user if values are invalid (exercise extension).
- Handle null values from
stdin.readLineSync()using the null coalescing operator.
Small Functions and Modularity
Wrap logic in functions so code is testable and reusable. Functions allow you to break down complex problems into smaller, manageable pieces.
Pattern
double calculateArea(double w, double h) => w * h;
void main() {
// get inputs...
double area = calculateArea(width, height);
print('Area: $area');
}
Benefits of Functions:
- Reusability: Write once, use multiple times
- Testability: Test functions independently
- Readability: Code becomes more self-documenting
- Maintainability: Changes in one place affect all usages
Common Pitfalls and Best Practices
Avoiding common mistakes will make your Dart code more robust and maintainable.
Null Safety
Don't assume stdin.readLineSync() returns non-null; always handle nullable results using the null coalescing operator or null checks.
Variable Mutability
Prefer final for variables that shouldn't change. This makes your intent clear and prevents accidental modifications.
Avoid Dynamic Types
Avoid dynamic unless necessary; prefer typed Map<String, dynamic> when parsing JSON to maintain type safety.
Input Validation
Always validate numeric parsing and provide clear error messages to users when input is invalid.